﻿using System.Collections.Generic;
using Common;
using System;
using WPFGestures.Gestures;

namespace Gestures
{
    public class CandidateLibrary
    {
        public Dictionary<String, Candidate> candidates;

        public CandidateLibrary()
        {
            candidates = new Dictionary<string, Candidate>();
        }

        public Candidate getCandidate(string name) {
            if (candidates.ContainsKey(name)) {
                return candidates[name];
            }

            return null;
        }

        public bool addCandidate(Candidate c)
        {
            // TODO: error checking.
            candidates.Add(c.name, c);
            return true;
        }

        
        // TODO: ugly global variables used for matching. Refactor.
        float angle = 0;
        float bestAngle = 0;

        public Match returnBestMatch(List<Point> l) {
            if (l == null) {
                throw new ArgumentNullException("Cannot match empty gesture" );
            }

            float bestScore = float.MaxValue;
            string bestName = null;

            // match the set of points with all candidates.
            Dictionary<String, Candidate>.ValueCollection.Enumerator e = candidates.Values.GetEnumerator();
            while (e.MoveNext()) {
                float score = compare(l, e.Current.transformedPoints, -45.0f, 45.0f, 2.0f);
                if (score < bestScore)
                {
                    bestScore = score;
                    bestAngle = angle;
                    bestName = e.Current.name;
                }
            }
            
            if (bestName == null)
            {
                Console.WriteLine("Library contains no gestures with same samplingrate.");
            }

            return new Match(bestName, bestScore);

            /* STUFF TO DETECT THE ANGLE OF THE MADE GESTURE.. */

            /*
            GestureTransformer tf = new GestureTransformer();

                        
                // what is brute force best angle?
                float naiveScore = float.MaxValue;
                int naiveAngle = 0;
                for (int i = 0; i < 360; ++i)
                {
                    float score = scoreAtAngle(l, candidates[bestIndex], (float) i);
                    if (score < naiveScore)
                    {
                        naiveScore = score;
                        naiveAngle = i;
                    }
                }

                //Console.WriteLine("best angle found using phi = " + bestAngle);
                //Console.WriteLine("best angle found using naive = " + naiveAngle);
                // (-bestAngle * (float)Math.PI) / 180.0f +
                //float angle = - candidateAngles[bestIndex] +
                float angle = (naiveAngle / 180.0f) * (float) Math.PI;
                List<Point> match = candidates[bestIndex];
                tf.scaleInPlace(match, candidateScales[bestIndex]);

                //return tf.rotate(candidates[bestIndex], new Point(0.0f, 0.0f), angle);

                //return bestIndex;
             * 
             */
            
        }


        public float compare(List<Point> l1, List<Point> l2, float leftangle, float rightangle, float angledelta)
        {
            if (l1 == null || l2 == null || l1.Count != l2.Count || l1.Count < 2)
            {
                return float.MaxValue;
                throw new ArgumentException("Cannot compare gestures with different samplingrate.");
            }

            // TODO: roteren moet efficienter. Ik maak nu voor elke rotatie een kopie van de gehele puntenset.
            // rotatie moet ook in place kunnen als ik het algoritme wat verander.

            float phi = ((float) Math.Sqrt(5) - 1.0f) / 2.0f;

            float leftphi = phi * leftangle + (1.0f - phi) * rightangle;
            float leftscore = scoreAtAngle(l1, l2, leftphi);

            float rightphi = (1.0f - phi) * leftangle + phi * rightangle;
            float rightscore = scoreAtAngle(l1, l2, rightphi);
            while (Math.Abs(rightangle - leftangle) > angledelta)
            {
                if (leftscore < rightscore)
                {

                    rightangle = rightphi;
                    rightphi = leftphi;
                    rightscore = leftscore;
                    leftphi = phi * leftangle + (1.0f - phi) * rightangle;
                    leftscore = scoreAtAngle(l1, l2, leftphi);
                }
                else
                {
                    leftangle = leftphi;
                    leftphi = rightphi;
                    leftscore = rightscore;
                    rightphi = (1.0f - phi) * leftangle + phi * rightangle;
                    rightscore = scoreAtAngle(l1, l2, rightphi);
                }
            }

            if (leftscore < rightscore)
            {
                angle = leftphi;
            }
            else
            {
                angle = rightphi;
            }

            return Math.Min(leftscore, rightscore);
        }


        public float scoreAtAngle(List<Point> l1, List<Point> l2, float angle)
        {

            Transformer tf = new Transformer();

            List<Point> rotated = tf.rotate(l1, new Point(0.0f, 0.0f), (angle * (float)Math.PI) / 180.0f);

            float averageDistance = 0.0f;

            for (int i = 0; i < l1.Count; ++i)
            {
                float dx = rotated[i].x - l2[i].x;
                float dy = rotated[i].y - l2[i].y;
                averageDistance += (float)Math.Sqrt(dx * dx + dy * dy);
            }

            // TODO: normalize score to bounding box
            return averageDistance;
        }


        /*
        public void loadGestures()
        {
            GestureTransformer t = new GestureTransformer();
            t.setResamplingRate(64);

            //float[] vierkant = {-0.5f, -0.5f, -0.5f, 0.5f, 0.05f, 0.5f, 0.5f, -0.5f, -0.5f, -0.5f};

            Point invScale;

            List<Common.Point> vierkant = new List<Common.Point>();
            vierkant.Add(new Common.Point(-0.5f, -0.5f));
            vierkant.Add(new Common.Point(-0.5f, 0.5f));
            vierkant.Add(new Common.Point(0.5f, 0.5f));
            vierkant.Add(new Common.Point(0.5f, -0.5f));
            vierkant.Add(new Common.Point(-0.5f, -0.5f));

            originals.Add(t.resample(vierkant));
            vierkant = t.transform(vierkant);
            candidates.Add(vierkant);
            Console.WriteLine("bounding box of rect: " + t.getWidth(vierkant) + ", " + t.getHeight(vierkant));
            candidateAngles.Add(t.getTransformAngle());
            invScale = t.getScaleFactor();
            invScale.x = 1.0f / invScale.x;
            invScale.y = 1.0f / invScale.y;
            candidateScales.Add(invScale);
            Console.WriteLine("scalefactor voor normalized form:  " + invScale.x + ", " + invScale.y);
            List<Point> vkrest = t.rotate(vierkant, new Point(0, 0), t.getTransformAngle());
            Console.WriteLine("bounding box of restore rect: " + t.getWidth(vkrest) + ", " + t.getHeight(vkrest));



            List<Common.Point> driehoek = new List<Common.Point>();
            driehoek.Add(new Common.Point(-0.5f, 0.0f));
            driehoek.Add(new Common.Point(0.0f, 1.0f));
            driehoek.Add(new Common.Point(0.5f, 0.0f));
            driehoek.Add(new Common.Point(-0.5f, 0.0f));
            originals.Add(t.resample(driehoek));
            driehoek = t.transform(driehoek);
            candidates.Add(driehoek);
            Console.WriteLine("driehoek trans angle = " + t.getTransformAngle());
            candidateAngles.Add(t.getTransformAngle());
            invScale = t.getScaleFactor();
            invScale.x = 1.0f / invScale.x;
            invScale.y = 1.0f / invScale.y;
            candidateScales.Add(invScale);

            List<Common.Point> lijn = new List<Common.Point>();
            lijn.Add(new Common.Point(0.0f, -0.5f));
            lijn.Add(new Common.Point(0.0f, 0.5f));
            originals.Add(t.resample(lijn));
            lijn = t.transform(lijn);
            candidates.Add(lijn);
            candidateAngles.Add(t.getTransformAngle());
            invScale = t.getScaleFactor();
            invScale.x = 1.0f / invScale.x;
            invScale.y = 1.0f / invScale.y;
            candidateScales.Add(invScale);

            List<Common.Point> haak = new List<Common.Point>();
            haak.Add(new Common.Point(0.0f, 0.0f));
            haak.Add(new Common.Point(1.0f, 0.0f));
            haak.Add(new Common.Point(1.0f, 1.0f));
            originals.Add(t.resample(haak));
            haak = t.transform(haak);
            candidates.Add(haak);
            candidateAngles.Add(t.getTransformAngle());
            invScale = t.getScaleFactor();
            invScale.x = 1.0f / invScale.x;
            invScale.y = 1.0f / invScale.y;
            candidateScales.Add(invScale);

            List<Common.Point> haak2 = new List<Common.Point>();
            haak2.Add(new Common.Point(0.0f, 0.0f));
            haak2.Add(new Common.Point(1.0f, 0.0f));
            haak2.Add(new Common.Point(1.0f, -1.0f));
            originals.Add(t.resample(haak2));
            haak2 = t.transform(haak2);
            candidates.Add(haak2);
            candidateAngles.Add(t.getTransformAngle());
            invScale = t.getScaleFactor();
            invScale.x = 1.0f / invScale.x;
            invScale.y = 1.0f / invScale.y;
            candidateScales.Add(invScale);

            List<Common.Point> cirkel = new List<Common.Point>();
            for (int i = 0; i < 64; ++i)
            {
                Common.Point p = new Common.Point();
                p.x = (float)Math.Sin(((2 * Math.PI) / 64.0f) * i);
                p.y = (float)Math.Cos(((2 * Math.PI) / 64.0f) * i);
                cirkel.Add(p);
            }
            //t.translateInPlace(cirkel, new Point(
            t.normalizeInPlace(cirkel, false);
            Console.WriteLine("bounding box of cirkel: " + t.getWidth(cirkel) + ", " + t.getHeight(cirkel));
            originals.Add(t.resample(cirkel));
            cirkel = t.transform(cirkel);
            candidates.Add(cirkel);
            candidateAngles.Add(t.getTransformAngle());
            invScale = t.getScaleFactor();
            invScale.x = 1.0f / invScale.x;
            invScale.y = 1.0f / invScale.y;
            candidateScales.Add(invScale);

            List<Common.Point> nui = new List<Common.Point>();
            for (int i = 0; i < 64; ++i)
            {
                Common.Point p = new Common.Point();
                p.x = i / 16.0f;
                p.y = (float)Math.Sin(((2 * Math.PI) / 64.0f) * i);
                nui.Add(p);
            }
            originals.Add(nui);
            nui = t.transform(nui);
            candidates.Add(nui);
            candidateAngles.Add(t.getTransformAngle());
            invScale = t.getScaleFactor();
            invScale.x = 1.0f / invScale.x;
            invScale.y = 1.0f / invScale.y;
            candidateScales.Add(invScale);


            List<Common.Point> dubbelnui = new List<Common.Point>();
            for (int i = 0; i < 64; ++i)
            {
                Common.Point p = new Common.Point();
                p.x = i / 16.0f;
                p.y = (float)Math.Sin(((2 * Math.PI) / 32.0f) * i);
                dubbelnui.Add(p);
            }
            originals.Add(t.resample(dubbelnui));
            dubbelnui = t.resample(dubbelnui);
            Point Center = t.calculateCenter(dubbelnui);
            Center.x = 0 - Center.x;
            Center.y = 0 - Center.y;
            t.translateInPlace(dubbelnui, Center);
            t.normalizeInPlace(dubbelnui, false);

            nui = t.transform(nui);
            Point prevScale = t.getScaleFactor();
            Console.WriteLine("prevScale:  " + prevScale.x + ", " + prevScale.y);
            dubbelnui = t.transform(dubbelnui);
            candidates.Add(dubbelnui);
            candidateAngles.Add(t.getTransformAngle());
            List<Point> restored = t.rotate(dubbelnui, new Point(0, 0), t.getTransformAngle());
            Console.WriteLine("bounding box of restored dubbelnui: " + t.getWidth(restored) + ", " + t.getHeight(restored));
            //invScale = t.getScaleFactor();
            //invScale.x =  invScale.x * prevScale.x;
            //invScale.y =  invScale.y * prevScale.y;
            //invScale.x = 1.0f / invScale.x;
            //invScale.y = 1.0f / invScale.y;
            candidateScales.Add(prevScale);

            Console.WriteLine("scalefactor voor dubbel nui form:  " + prevScale.x + ", " +  prevScale.y);

            List<Common.Point> ster = new List<Common.Point>();
            ster.Add(new Common.Point(0.0f, 0.0f));
            ster.Add(new Common.Point(1.0f, 2.0f));
            ster.Add(new Common.Point(2.0f, 0.0f));
            ster.Add(new Common.Point(0.0f, 1.3f));
            ster.Add(new Common.Point(2.0f, 1.3f));
            ster.Add(new Common.Point(0.0f, 0.0f));
            originals.Add(t.resample(ster));
            ster = t.transform(ster);
            candidates.Add(ster);
            candidateAngles.Add(t.getTransformAngle());
            invScale = t.getScaleFactor();
            invScale.x = 1.0f / invScale.x;
            invScale.y = 1.0f / invScale.y;
            candidateScales.Add(invScale);
        }*/
    }
}